package com.qianfeng.iot.service.impl;

//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼                  BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  
//  


import cn.hutool.core.lang.UUID;
import com.dc3.common.bean.R;
import com.dc3.common.constant.CacheConstant;
import com.dc3.common.model.User;
import com.dc3.common.utils.Dc3Util;
import com.dc3.common.utils.KeyUtil;
import com.qianfeng.iot.openfeign.CacheFeignClient;
import com.qianfeng.iot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * Created by Jackiechan on 2022/7/29 20:11
 *
 * @author Jackiechan
 * @version 1.0
 * @since 1.0
 */
@Service
@Transactional
public class UserServiceImpl implements UserService {


    private CacheFeignClient cacheFeignClient;

    @Autowired
    public void setCacheFeignClient(CacheFeignClient cacheFeignClient) {
        this.cacheFeignClient = cacheFeignClient;
    }

    private R2dbcEntityTemplate r2dbcEntityTemplate;

    @Autowired
    public void setR2dbcEntityTemplate(R2dbcEntityTemplate r2dbcEntityTemplate) {
        this.r2dbcEntityTemplate = r2dbcEntityTemplate;
    }

    @Override
    public Mono<R<Object>> addUser(Mono<User> user) {
        //对数据进行校验和转换,比如用户名不能为空,密码必须设置为MD5值等等
        return user.filter(user1 -> StringUtils.hasText(user1.getName())
                        && StringUtils.hasText(user1.getPassword())
                        && StringUtils.hasText(user1.getPhone())
                        && StringUtils.hasText(user1.getEmail()))
                //原本我们应该是在这里进行过滤的,但是我们实在是无法得到一个boolean类型的结果
                //所以我们换了一个思路,进行数据转换,就是只有当没有结果的时候我们才会把原始数据返回,否则我们什么都不做
                .flatMap(user1 -> {
                            //我们还要进行一次过滤,检查当前用户名是不是已经注册过了
                            //我们的用户是绑定到租户上面的,同一个租户下面不可以重复,不同的租户是可以重复用户名的
                            //不能查询到一条数据,否则就提示用户已经被注册
                            return findByNameOrPhoneOrEmailAndTenantId(user1.getName(), user1.getPhone(), user1.getEmail(), user1.getTenantId())
                                    //获取到数据的数量
                                    .count()
                                    //我们将它的查询的数量进行判断,如果数量是0 就代表没有结果
                                    .filter(count -> count == 0)
                                    //只有在上面没有结果的情况下我们才会返回一个数据,这个数据就是我们当前要操作的数据
                                    .map(count -> user1);
                        }

                )
                //设置主键,因为用户的主键并不是自增的
                .map(user1 -> {
                    //设置了主键
                    user1.setId(UUID.randomUUID().toString(true));
                    //看看有没有传递描述,有就用传递的,没有就用默认的
                    user1.setDescription(StringUtils.hasText(user1.getDescription()) ? user1.getDescription() : "");
                    //将密码修改为md5
                    user1.setPassword(Dc3Util.md5(user1.getPassword()));
                    return user1;
                })
                //插入到数据库
                .flatMap(user1 -> r2dbcEntityTemplate.insert(User.class).into("dc3_user").using(user1))
                //我们还没有讲用户绑定到租户上
                .map(user1 -> R.ok())
                .defaultIfEmpty(R.fail("请检查数据"));
    }

    @Override
    public Flux<User> findByNameOrPhoneOrEmailAndTenantId(String name, String phone, String email, String tenantId) {
        return r2dbcEntityTemplate
                .select(User.class).from("dc3_user")
                .matching(Query.query(
                        Criteria.where("name").is(name)
                                .or(Criteria.where("phone").is(phone))
                                .or(Criteria.where("email").is(email))
                                .and(Criteria.where("tenantId").is(tenantId)))
                ).all();
    }

    @Override
    public Mono<User> findById(String id) {
        return r2dbcEntityTemplate.select(User.class).from("dc3_user")
                .matching(Query.query(Criteria.where("id").is(id)).columns("name", "phone", "email", "description", "tenant_id"))
                //根据id查询有且只能有一个,所以写first
                .first();
    }

    @Override
    public Mono<String> createToken(Mono<User> userMono) {
        return userMono.filter(user ->
                        StringUtils.hasText(user.getName())
                                && StringUtils.hasText(user.getPassword())
                                && StringUtils.hasText(user.getTenantId())
                ).flatMap(user ->
                        r2dbcEntityTemplate.select(User.class).from("dc3_user")
                                //设置查询条件,根据用户名和租户id查询,不包括密码
                                .matching(Query.query(
                                        Criteria.where("name").is(user.getName())
                                                //  .and(Criteria.where("password").is(user.getPassword()))
                                                .and(Criteria.where("tenant_id").is(user.getTenantId()))
                                ))
                                //判断查询出来的密码是不是和传递的密码是一样的
                                .first().filter(user1 -> user1.getPassword().equals(Dc3Util.md5(user.getPassword())))
                ) //生成令牌
                .map(user -> {
                    //每次都生成一个随机的盐
                    String salt = UUID.randomUUID().toString(true);
                    //保存盐.比如我们保存到redis中
                    cacheFeignClient.setValue(user.getTenantId() + CacheConstant.Suffix.TENANT_ID + CacheConstant.Entity.USER + user.getName() + CacheConstant.Suffix.SALT, salt).subscribe();
                    return KeyUtil.generateToken(user.getName()+"&"+user.getTenantId(), salt);
                });

    }
}
